﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Orchard;
using Orchard.Environment.Features;
using RaisingStudio.Razor.Services;
using System.Diagnostics;
using RaisingStudio.Trigger.Models;
using Orchard.ContentManagement;
using Orchard.Data;
using Orchard.ContentManagement.Records;
using Orchard.Core.Contents.ViewModels;
using Orchard.Logging;

namespace RaisingStudio.Trigger.Services
{
    public class TriggerService : ITriggerService
    {
        private IRazorService razorService;
        private IOrchardServices orchardServices;
        private IFeatureManager featureManager;

        public TriggerService(IOrchardServices orchardServices, IFeatureManager featureManager, IRazorService razorService)
        {
            this.orchardServices = orchardServices;
            this.featureManager = featureManager;
            this.razorService = razorService;

            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        private bool existingTriggerRegistred = false;
        private bool existingTriggerRegistring = false;

        private void RegisterTriggers(IOrchardServices orchardServices)
        {
            try
            {
                lock (this.triggers_lock)
                {
                    if ((!this.existingTriggerRegistred) && (!this.existingTriggerRegistring))
                    {
                        this.existingTriggerRegistring = true;
                        // Register triggers from existing database.
                        var query = orchardServices.ContentManager.Query<TriggerPart>(Migrations.TRIGGERS_CONTENTTYPE);
                        foreach (var triggerPart in query.List())
                        {
                            TriggerModel triggerModel = new TriggerModel
                            {
                                Id = triggerPart.Id,
                                Title = triggerPart.Title,
                                Event = triggerPart.Event,
                                Condition = triggerPart.Condition,
                                Action = triggerPart.Action,
                                Enable = (new PublishContentViewModel(triggerPart.ContentItem)).HasPublished
                            };
                            RegisterTrigger(triggerModel);
                        }
                        this.existingTriggerRegistring = false;
                        this.existingTriggerRegistred = true;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
                Logger.Error(ex, "An error occurred while register triggers.");
            }
        }

        private object triggers_lock = new object();
        private Dictionary<string, List<TriggerModel>> triggerEvents = new Dictionary<string, List<TriggerModel>>();
        private Dictionary<int, TriggerModel> triggers = new Dictionary<int, TriggerModel>();

        public void RegisterTrigger(TriggerModel trigger)
        {
            lock (this.triggers_lock)
            {
                int triggerId = trigger.Id;
                string triggerEvent = trigger.Event;
                if (this.triggers.ContainsKey(triggerId))
                {
                    #region Existing trigger.
                    TriggerModel existingTrigger = this.triggers[triggerId];
                    string existingTriggerEvent = existingTrigger.Event;
                    if (triggerEvent != existingTriggerEvent)
                    {
                        #region Remove the exising trigger.
                        if (this.triggerEvents.ContainsKey(existingTriggerEvent))
                        {
                            var triggerModels = this.triggerEvents[existingTriggerEvent];
                            if (triggerModels != null)
                            {
                                var triggerModel = triggerModels.SingleOrDefault(t => t.Id == triggerId);
                                if (triggerModel != null)
                                {
                                    triggerModels.Remove(triggerModel);
                                }
                            }
                        }
                        #endregion
                        if (!string.IsNullOrEmpty(triggerEvent))
                        {
                            #region Update the triggeer.
                            existingTrigger.Title = trigger.Title;
                            existingTrigger.Event = triggerEvent;
                            existingTrigger.Condition = trigger.Condition;
                            existingTrigger.Action = trigger.Action;
                            #endregion
                            #region Add the trigger.
                            if (this.triggerEvents.ContainsKey(triggerEvent))
                            {
                                this.triggerEvents[triggerEvent].Add(existingTrigger);
                            }
                            else
                            {
                                this.triggerEvents.Add(triggerEvent, new List<TriggerModel>(new TriggerModel[] { existingTrigger }));
                            }
                            #endregion
                        }
                        else
                        {
                            this.triggers.Remove(triggerId);
                        }
                    }
                    else
                    {
                        #region Update the trigger.
                        existingTrigger.Title = trigger.Title;
                        existingTrigger.Condition = trigger.Condition;
                        existingTrigger.Action = trigger.Action;
                        #endregion
                    }
                    #endregion
                }
                else
                {
                    #region New trigger.
                    if (!string.IsNullOrEmpty(triggerEvent))
                    {
                        #region Clone the triggeer.
                        TriggerModel triggerClone = new TriggerModel
                        {
                            Id = triggerId,
                            Title = trigger.Title,
                            Event = triggerEvent,
                            Condition = trigger.Condition,
                            Action = trigger.Action,
                            Enable = trigger.Enable
                        };
                        #endregion
                        #region Add the trigger.
                        if (this.triggerEvents.ContainsKey(triggerEvent))
                        {
                            this.triggerEvents[triggerEvent].Add(triggerClone);
                        }
                        else
                        {
                            this.triggerEvents.Add(triggerEvent, new List<TriggerModel>(new TriggerModel[] { triggerClone }));
                        }
                        #endregion

                        this.triggers.Add(triggerId, triggerClone);
                    }
                    #endregion
                }
            }
        }

        public void RegisterTrigger(TriggerModel trigger, IOrchardServices orchardServices)
        {
            RegisterTriggers(orchardServices);
            RegisterTrigger(trigger);
        }

        public void RemoveTrigger(TriggerModel trigger)
        {
            lock (this.triggers_lock)
            {
                int triggerId = trigger.Id;
                if (this.triggers.ContainsKey(triggerId))
                {
                    foreach (var triggerEvent in this.triggerEvents.Keys)
                    {
                        if (this.triggerEvents[triggerEvent] != null)
                        {
                            this.triggerEvents[triggerEvent].RemoveAll(t => t.Id == triggerId);
                        }
                    }
                }
            }
        }

        public void RemoveTrigger(TriggerModel trigger, IOrchardServices orchardServices)
        {
            RegisterTriggers(orchardServices);
            RemoveTrigger(trigger);
        }


        private void ChangeTriggerStatus(TriggerModel trigger, bool enable)
        {
            lock (this.triggers_lock)
            {
                int triggerId = trigger.Id;
                string triggerEvent = trigger.Event;
                if (this.triggers.ContainsKey(triggerId))
                {
                    #region Existing trigger.
                    TriggerModel existingTrigger = this.triggers[triggerId];
                    existingTrigger.Enable = enable;
                    #endregion
                }
            }
        }

        public void EnableTrigger(TriggerModel trigger)
        {
             ChangeTriggerStatus(trigger, true);
       }
        
        public void EnableTrigger(TriggerModel trigger, IOrchardServices orchardServices)
        {
            RegisterTriggers(orchardServices);
            EnableTrigger(trigger);
        }

        public void DisableTrigger(TriggerModel trigger)
        {
            ChangeTriggerStatus(trigger, false);
        }

        public void DisableTrigger(TriggerModel trigger, IOrchardServices orchardServices)
        {
            RegisterTriggers(orchardServices);
            DisableTrigger(trigger);
        }

        public void HandleEvent(string triggerEvent, IOrchardServices orchardServices, dynamic content, dynamic context)
        {
            RegisterTriggers(orchardServices);

            List<TriggerModel> triggersClone = null;
            lock (this.triggers_lock)
            {
                if ((this.triggerEvents.ContainsKey(triggerEvent)) && (this.triggerEvents[triggerEvent] != null))
                {
                    triggersClone = new List<TriggerModel>(this.triggerEvents[triggerEvent].Select(t => new TriggerModel
                    {
                        Id = t.Id,
                        Title = t.Title,
                        Event = t.Event,
                        Condition = t.Condition,
                        Action = t.Action,
                        Enable = t.Enable
                    }));
                }
            }
            if (triggersClone != null)
            {
                foreach (TriggerModel trigger in triggersClone)
                {
                    if (trigger.Enable)
                    {
                        string conditionResult = null;
                        string actionResult = null;
                        DateTime beginTime = DateTime.Now;
                        DateTime endTime = DateTime.Now;
                        try
                        {
                            conditionResult = this.razorService.Run(trigger.Condition, orchardServices, content, context);
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine(ex);
                            Logger.Error(ex, "An error occurred while RazorService.Run for trigger condition.");
                        }
                        bool condition = false;
                        if ((!string.IsNullOrEmpty(conditionResult)) && (bool.TryParse(conditionResult.Trim(), out condition)) && (condition))
                        {
                            try
                            {
                                beginTime = DateTime.Now;
                                actionResult = this.razorService.Run(trigger.Action, orchardServices, content, context);
                                endTime = DateTime.Now;
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex);
                                Logger.Error(ex, "An error occurred while RazorService.Run for trigger action.");
                            }
                        }

                        // Add TriggerLog.                        
                        CreateTriggerLog(trigger, orchardServices, content, condition, conditionResult, actionResult, beginTime, endTime);
                    }
                }
            }
        }

        private bool IsTriggerLogEnable(IOrchardServices services)
        {
            // TODO:
            if (services.ContentManager.GetContentTypeDefinitions().Any(c => c.Name == Migrations.TRIGGERLOGS_CONTENTTYPE))
            {
                return true;
            }
            return false;
        }

        private void CreateTriggerLog(TriggerModel trigger, IOrchardServices services, dynamic content, bool condition, string conditionResult, string actionResult, DateTime beginTime, DateTime endTime)
        {
            try
            {
                if (IsTriggerLogEnable(services))
                {
                    var triggerLogPart = services.ContentManager.Create<TriggerLogPart>(Migrations.TRIGGERLOGS_CONTENTTYPE);
                    triggerLogPart.TriggerId = trigger.Id;
                    triggerLogPart.Title = trigger.Title;
                    triggerLogPart.Event = trigger.Event;
                    triggerLogPart.Condition = trigger.Condition;
                    triggerLogPart.Action = trigger.Action;
                    triggerLogPart.ContextContentType = (content != null) ? content.ContentType : null;
                    triggerLogPart.ContextContentId = (content != null) ? Convert.ToString(content.Id) : null;
                    triggerLogPart.Status = Convert.ToString(condition);
                    triggerLogPart.ConditionResult = conditionResult;
                    triggerLogPart.ActionResult = actionResult;
                    triggerLogPart.TriggerBeginTime = beginTime.ToString();
                    triggerLogPart.TriggerEndTime = endTime.ToString();
                    services.ContentManager.Publish(triggerLogPart.ContentItem);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
                Logger.Error(ex, "An error occurred while create trigger log.");
            }
        }
    }
}
